Skip to content

feat: add Lobstr wallet, dynamic portfolio comparison, OG meta tags, … - #1615

Merged
ritik4ever merged 2 commits into
ritik4ever:mainfrom
yadavaman8960:feat/multiple-enhancements
Jul 30, 2026
Merged

feat: add Lobstr wallet, dynamic portfolio comparison, OG meta tags, …#1615
ritik4ever merged 2 commits into
ritik4ever:mainfrom
yadavaman8960:feat/multiple-enhancements

Conversation

@yadavaman8960

@yadavaman8960 yadavaman8960 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

closes #1443
closes #1444
closes #1445
closes #1449

Summary by CodeRabbit

  • New Features
    • Added support for connecting to the Lobstr wallet, including correct installed-status detection.
    • Increased portfolio comparison capacity from three to five portfolios.
    • Added URL-configurable embedded widgets (size and theme).
    • Added social sharing metadata updates for public portfolio pages.
  • Bug Fixes
    • Improved wallet selection and embed widget styling consistency across sizes/themes.
  • Tests
    • Added/expanded tests for wallet selection, comparison page, embedded widgets, and shared portfolio rendering.
  • Chores
    • Improved test reliability by mocking missing browser APIs in test environments.

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@yadavaman8960 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 85e4d495-417b-40af-b145-3e36b8b3b24d

📥 Commits

Reviewing files that changed from the base of the PR and between cbe3cd2 and 90ec661.

📒 Files selected for processing (1)
  • frontend/src/utils/walletAdapters.ts

📝 Walkthrough

Walkthrough

Changes

The frontend adds Lobstr wallet detection, expands portfolio comparison to five selections, introduces size/theme query parameters for the embed widget, and injects Open Graph metadata for shared portfolios. Vitest coverage validates these behaviors, with a ResizeObserver test stub and the react-router-dom dependency added.

Lobstr wallet support

Layer / File(s) Summary
Wallet contract and picker behavior
frontend/src/utils/*, frontend/src/components/WalletPicker.tsx, frontend/src/components/WalletPicker.test.tsx
Replaces Hana with Lobstr in wallet types and registry configuration, adds browser typings and installation detection, and tests picker rendering and connection callbacks.

Portfolio comparison expansion

Layer / File(s) Summary
Selection limits and comparison validation
frontend/src/pages/Compare.tsx, frontend/src/pages/__tests__/Compare.test.tsx, frontend/package.json
Raises the selection limit to five, updates the layout threshold and count indicator, and tests multi-portfolio comparison states.

Configurable embed widget

Layer / File(s) Summary
Query parsing and responsive rendering
frontend/src/pages/EmbedWidget.tsx, frontend/src/pages/__tests__/EmbedWidget.test.tsx, frontend/src/test/setup.ts
Parses validated size and theme parameters and applies them to widget styling, chart dimensions, render states, and tested output.

Public portfolio metadata

Layer / File(s) Summary
Dynamic metadata and coverage
frontend/src/pages/PublicPortfolio.tsx, frontend/src/pages/__tests__/PublicPortfolio.test.tsx
Adds portfolio-derived title and Open Graph tags with cleanup, and tests loading, error, success, and metadata behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EmbedWidget
  participant URLSearchParams
  participant api
  participant Chart
  EmbedWidget->>URLSearchParams: read size and theme
  URLSearchParams-->>EmbedWidget: validated widget configuration
  EmbedWidget->>api: load portfolio data
  api-->>EmbedWidget: portfolio and asset data
  EmbedWidget->>Chart: render size-based chart geometry
Loading

Possibly related PRs

Suggested reviewers: xqcxx

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only lists closing issues and omits the required summary, change type, and checklist details from the template. Add a change summary, type-of-change selection, and the required checklist items; keep the linked issue references.
Linked Issues check ⚠️ Warning [#1444] adds OG tags but no shareable preview image generation, and [#1443] lacks the documented query-parameter docs. Add shareable preview image generation for PublicPortfolio and document EmbedWidget size/theme query parameters before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main changes: Lobstr wallet, compare expansion, embed params, and OG metadata.
Out of Scope Changes check ✅ Passed All listed changes align with the stated objectives; no unrelated feature or refactor stands out.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.5)
frontend/src/utils/walletAdapters.ts

File contains syntax errors that prevent linting: Line 220: Expected a statement but instead found '}

async connect(): Promise<string>'.; Line 228: Illegal return statement outside of a function; Line 229: Expected a statement but instead found 'catch (error)'.; Line 234: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 234: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 235: Illegal return statement outside of a function; Line 239: Illegal return statement outside of a function; Line 243: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 243: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 246: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 246: expected `,` but instead found `:`; Line 246: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 249: Expected a statement but instead found 'catch (error)'.; Line 253: Expected a statement but instead found '}'.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
frontend/src/components/WalletPicker.test.tsx (2)

16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Do not mock the production wallet list for this assertion.

Because SUPPORTED_WALLETS is replaced with a hand-written mock, the “renders all supported wallet options” test can pass even if the real configuration omits Lobstr. Use the production list or add a focused configuration test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/WalletPicker.test.tsx` around lines 16 - 27, Update
the WalletPicker test setup to preserve the production SUPPORTED_WALLETS list
instead of replacing it with a hand-written mock. Mock only the wallet state
helpers such as getLastUsedWallet, setLastUsedWallet, and clearLastUsedWallet,
so the “renders all supported wallet options” assertion validates the real
configuration, including Lobstr.

57-80: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add adapter-level Lobstr tests.

These tests use an empty provider object and mock walletManager.connect, so they never exercise LobstrAdapter.connect, isConnected, signTransaction, or disconnect. Add unit tests for those adapter methods and their failure paths; otherwise the picker tests can pass while the wallet integration remains broken.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/WalletPicker.test.tsx` around lines 57 - 80, Add
focused unit tests for LobstrAdapter covering connect, isConnected,
signTransaction, and disconnect, including each method’s failure paths. Use a
realistic mocked Lobstr provider and assert the adapter’s provider calls,
returned values, and propagated or handled errors; keep the existing
WalletPicker tests unchanged.
frontend/src/pages/__tests__/PublicPortfolio.test.tsx (1)

32-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the complete metadata lifecycle.

The test only partially checks the OG contract and its afterEach manually deletes tags, so it does not verify component cleanup. Assert document.title, og:type, og:site_name, the exact URL, and og:image; unmount the component and verify the previous title/tags are restored.

Also applies to: 60-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/__tests__/PublicPortfolio.test.tsx` around lines 32 - 36,
Update the PublicPortfolio metadata test around the component render and
afterEach cleanup to cover the complete lifecycle: assert document.title,
og:type, og:site_name, the exact URL, and og:image values; unmount the rendered
component, then verify the pre-existing title and metadata tags are restored.
Remove the manual OG-tag deletion from afterEach so the test validates component
cleanup rather than masking it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/pages/__tests__/Compare.test.tsx`:
- Around line 57-66: Add a sixth portfolio fixture to mockPortfolios, then
update the existing selection test to click every card and assert the count
remains “5/5 selected,” ensuring the sixth selection is rejected. Keep the test
focused on the Compare component’s selection limit.
- Around line 68-86: Strengthen the test around Compare’s three-portfolio
selection by asserting mockUsePortfolioCompare receives all three selected
portfolio IDs, not merely that it was called. Also assert the rendered
comparison table includes headers for Growth Portfolio, Income Portfolio, and
Balanced Portfolio so the test detects any two-selection cap.

In `@frontend/src/pages/Compare.tsx`:
- Line 41: Cap portfolio IDs initialized from the URL to five before they are
stored or passed to usePortfolioCompare. Update the URL parsing/initialization
logic near the selectedPortfolioIds setup, while preserving the existing
selectedPortfolioIds.length click guard and ensuring the displayed count cannot
exceed 5.

In `@frontend/src/pages/EmbedWidget.tsx`:
- Around line 105-113: Update the loading and error/unavailable branches in
EmbedWidget to use the existing isDark value when selecting background and text
styling, instead of relying only on Tailwind dark: classes. Preserve the current
successful-data behavior and add regression coverage verifying dark-theme
loading and error rendering.

In `@frontend/src/pages/PublicPortfolio.tsx`:
- Around line 6-22: Update setMetaTags and removeMetaTags to preserve each tag’s
pre-existing state: capture prior title/content values before overwriting, track
which meta elements were created by this component, restore captured values
during cleanup, and remove only component-created tags. Ensure the cleanup also
restores the original document title instead of using a hard-coded replacement.
- Around line 70-82: The PublicPortfolio page currently injects
portfolio-specific metadata only inside the client-side useEffect, so crawlers
receive generic tags. Move this metadata generation into the
server-rendered/HTML response path using the fetched portfolio data, and add an
absolute og:image preview URL while preserving the existing title, description,
URL, type, and site name values.

In `@frontend/src/utils/stellar.ts`:
- Around line 20-24: The LOBSTR integration uses the wrong provider global and
API. In frontend/src/utils/stellar.ts lines 20-24,
frontend/src/utils/walletAdapters.ts lines 218-245 and 254-265, and
frontend/src/components/WalletPicker.tsx line 30, replace window.lobstr and
requestAccess with window.lobstrSignerExtensionApi, using getPublicKey(),
isConnected(), and signTransaction(xdr) for detection, connection, and signing
while preserving the existing adapter and picker behavior.

---

Nitpick comments:
In `@frontend/src/components/WalletPicker.test.tsx`:
- Around line 16-27: Update the WalletPicker test setup to preserve the
production SUPPORTED_WALLETS list instead of replacing it with a hand-written
mock. Mock only the wallet state helpers such as getLastUsedWallet,
setLastUsedWallet, and clearLastUsedWallet, so the “renders all supported wallet
options” assertion validates the real configuration, including Lobstr.
- Around line 57-80: Add focused unit tests for LobstrAdapter covering connect,
isConnected, signTransaction, and disconnect, including each method’s failure
paths. Use a realistic mocked Lobstr provider and assert the adapter’s provider
calls, returned values, and propagated or handled errors; keep the existing
WalletPicker tests unchanged.

In `@frontend/src/pages/__tests__/PublicPortfolio.test.tsx`:
- Around line 32-36: Update the PublicPortfolio metadata test around the
component render and afterEach cleanup to cover the complete lifecycle: assert
document.title, og:type, og:site_name, the exact URL, and og:image values;
unmount the rendered component, then verify the pre-existing title and metadata
tags are restored. Remove the manual OG-tag deletion from afterEach so the test
validates component cleanup rather than masking it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee4d41a-f318-492d-a42b-b9710020ebca

📥 Commits

Reviewing files that changed from the base of the PR and between 310e8df and cbe3cd2.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • frontend/package.json
  • frontend/src/components/WalletPicker.test.tsx
  • frontend/src/components/WalletPicker.tsx
  • frontend/src/pages/Compare.tsx
  • frontend/src/pages/EmbedWidget.tsx
  • frontend/src/pages/PublicPortfolio.tsx
  • frontend/src/pages/__tests__/Compare.test.tsx
  • frontend/src/pages/__tests__/EmbedWidget.test.tsx
  • frontend/src/pages/__tests__/PublicPortfolio.test.tsx
  • frontend/src/test/setup.ts
  • frontend/src/utils/stellar.ts
  • frontend/src/utils/walletAdapters.ts

Comment on lines +57 to +66
it('allows selecting up to 5 portfolios and shows correct count', () => {
render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />)

const allCards = mockPortfolios.map(p => screen.getByText(p.name).closest('div'))
for (const card of allCards) {
if (card) fireEvent.click(card)
}

expect(screen.getByText('5/5 selected')).toBeTruthy()
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test rejection of a sixth selection.

This test has only five cards, so it still passes if the limit changes to six. Add a sixth fixture, click all six cards, and keep the expected count at 5/5 selected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/__tests__/Compare.test.tsx` around lines 57 - 66, Add a
sixth portfolio fixture to mockPortfolios, then update the existing selection
test to click every card and assert the count remains “5/5 selected,” ensuring
the sixth selection is rejected. Keep the test focused on the Compare
component’s selection limit.

Comment on lines +68 to +86
it('renders comparison table for 3 portfolios', async () => {
render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />)

const card1 = screen.getByText('Growth Portfolio').closest('div')
const card2 = screen.getByText('Income Portfolio').closest('div')
const card3 = screen.getByText('Balanced Portfolio').closest('div')
if (card1) fireEvent.click(card1)
if (card2) fireEvent.click(card2)
if (card3) fireEvent.click(card3)

await waitFor(() => {
expect(mockUsePortfolioCompare).toHaveBeenCalled()
expect(screen.getByText('Total Return')).toBeTruthy()
expect(screen.getByText('Volatility')).toBeTruthy()
expect(screen.getByText('Max Drawdown')).toBeTruthy()
expect(screen.getByText('Sharpe Ratio')).toBeTruthy()
expect(screen.getByText('Rebalance Count')).toBeTruthy()
expect(screen.getByText('Portfolio Value')).toBeTruthy()
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify that all three selected IDs drive the comparison.

The mocked response always contains three portfolios, so this passes even if selection is capped at two. Assert the hook receives the three IDs and that the table has all three portfolio headers.

Proposed assertions
 await waitFor(() => {
-  expect(mockUsePortfolioCompare).toHaveBeenCalled()
+  expect(mockUsePortfolioCompare).toHaveBeenLastCalledWith([
+    'portfolio-1',
+    'portfolio-2',
+    'portfolio-3',
+  ])
   expect(screen.getByText('Total Return')).toBeTruthy()
 })
+
+expect(screen.getByRole('columnheader', { name: 'Growth Portfolio' })).toBeTruthy()
+expect(screen.getByRole('columnheader', { name: 'Income Portfolio' })).toBeTruthy()
+expect(screen.getByRole('columnheader', { name: 'Balanced Portfolio' })).toBeTruthy()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('renders comparison table for 3 portfolios', async () => {
render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />)
const card1 = screen.getByText('Growth Portfolio').closest('div')
const card2 = screen.getByText('Income Portfolio').closest('div')
const card3 = screen.getByText('Balanced Portfolio').closest('div')
if (card1) fireEvent.click(card1)
if (card2) fireEvent.click(card2)
if (card3) fireEvent.click(card3)
await waitFor(() => {
expect(mockUsePortfolioCompare).toHaveBeenCalled()
expect(screen.getByText('Total Return')).toBeTruthy()
expect(screen.getByText('Volatility')).toBeTruthy()
expect(screen.getByText('Max Drawdown')).toBeTruthy()
expect(screen.getByText('Sharpe Ratio')).toBeTruthy()
expect(screen.getByText('Rebalance Count')).toBeTruthy()
expect(screen.getByText('Portfolio Value')).toBeTruthy()
})
it('renders comparison table for 3 portfolios', async () => {
render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />)
const card1 = screen.getByText('Growth Portfolio').closest('div')
const card2 = screen.getByText('Income Portfolio').closest('div')
const card3 = screen.getByText('Balanced Portfolio').closest('div')
if (card1) fireEvent.click(card1)
if (card2) fireEvent.click(card2)
if (card3) fireEvent.click(card3)
await waitFor(() => {
expect(mockUsePortfolioCompare).toHaveBeenLastCalledWith([
'portfolio-1',
'portfolio-2',
'portfolio-3',
])
expect(screen.getByText('Total Return')).toBeTruthy()
expect(screen.getByText('Volatility')).toBeTruthy()
expect(screen.getByText('Max Drawdown')).toBeTruthy()
expect(screen.getByText('Sharpe Ratio')).toBeTruthy()
expect(screen.getByText('Rebalance Count')).toBeTruthy()
expect(screen.getByText('Portfolio Value')).toBeTruthy()
})
expect(screen.getByRole('columnheader', { name: 'Growth Portfolio' })).toBeTruthy()
expect(screen.getByRole('columnheader', { name: 'Income Portfolio' })).toBeTruthy()
expect(screen.getByRole('columnheader', { name: 'Balanced Portfolio' })).toBeTruthy()
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/__tests__/Compare.test.tsx` around lines 68 - 86,
Strengthen the test around Compare’s three-portfolio selection by asserting
mockUsePortfolioCompare receives all three selected portfolio IDs, not merely
that it was called. Also assert the rendered comparison table includes headers
for Growth Portfolio, Income Portfolio, and Balanced Portfolio so the test
detects any two-selection cap.

if (selectedPortfolioIds.includes(portfolioId)) {
setSelectedPortfolioIds(prev => prev.filter(id => id !== portfolioId))
} else if (selectedPortfolioIds.length < 3) {
} else if (selectedPortfolioIds.length < 5) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cap URL-initialized selections as well.

Line 41 only limits clicks. ?portfolios=a,b,c,d,e,f initializes six IDs at Lines 20-23, displays 6/5, and sends all six to usePortfolioCompare.

Proposed fix
 const COLORS = ['`#3B82F6`', '`#10B981`', '`#F59E0B`', '`#EF4444`', '`#8B5CF6`', '`#EC4899`']
+const MAX_SELECTED_PORTFOLIOS = 5

 const [selectedPortfolioIds, setSelectedPortfolioIds] = useState<string[]>(() => {
   const params = searchParams.get('portfolios')
-  return params ? params.split(',') : []
+  return [...new Set(params?.split(',').filter((id) => id.length > 0) ?? [])]
+    .slice(0, MAX_SELECTED_PORTFOLIOS)
 })

-    } else if (selectedPortfolioIds.length < 5) {
+    } else if (selectedPortfolioIds.length < MAX_SELECTED_PORTFOLIOS) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (selectedPortfolioIds.length < 5) {
} else if (selectedPortfolioIds.length < MAX_SELECTED_PORTFOLIOS) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Compare.tsx` at line 41, Cap portfolio IDs initialized
from the URL to five before they are stored or passed to usePortfolioCompare.
Update the URL parsing/initialization logic near the selectedPortfolioIds setup,
while preserving the existing selectedPortfolioIds.length click guard and
ensuring the displayed count cannot exceed 5.

Comment on lines +105 to +113
<div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}>
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
</div>
)
}

if (error || !data) {
return (
<div className="h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden">
<div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor theme in loading and unavailable states.

theme=dark only affects the successful-data branch. These branches rely on Tailwind dark: mode, so a dark embed can render light (and vice versa) until data loads or after an error. Use isDark here too, and add a regression test for dark loading/error rendering.

Proposed fix
- <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}>
+ <div className={`h-screen w-full ${isDark ? 'bg-slate-950' : 'bg-white'} flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}>

- <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}>
-   <p className="text-sm text-slate-500 dark:text-slate-400 font-medium">
+ <div className={`h-screen w-full ${isDark ? 'bg-slate-950' : 'bg-white'} flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}>
+   <p className={`${s.asset} ${isDark ? 'text-slate-400' : 'text-slate-500'} font-medium`}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/EmbedWidget.tsx` around lines 105 - 113, Update the
loading and error/unavailable branches in EmbedWidget to use the existing isDark
value when selecting background and text styling, instead of relying only on
Tailwind dark: classes. Preserve the current successful-data behavior and add
regression coverage verifying dark-theme loading and error rendering.

Comment on lines +6 to +22
function setMetaTags(tags: Record<string, string>) {
for (const [property, content] of Object.entries(tags)) {
let el = document.querySelector(`meta[property="${property}"]`)
if (!el) {
el = document.createElement('meta')
el.setAttribute('property', property)
document.head.appendChild(el)
}
el.setAttribute('content', content)
}
}

function removeMetaTags(properties: string[]) {
for (const property of properties) {
const el = document.querySelector(`meta[property="${property}"]`)
if (el) el.remove()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore existing head state during cleanup.

setMetaTags overwrites any existing global tags, but cleanup always removes them and replaces the prior title with a hard-coded value. Capture the previous title/content and remove only tags created by this component; otherwise navigating away can erase metadata owned by the app shell or another route.

Also applies to: 83-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/PublicPortfolio.tsx` around lines 6 - 22, Update
setMetaTags and removeMetaTags to preserve each tag’s pre-existing state:
capture prior title/content values before overwriting, track which meta elements
were created by this component, restore captured values during cleanup, and
remove only component-created tags. Ensure the cleanup also restores the
original document title instead of using a hard-coded replacement.

Comment on lines +70 to +82
useEffect(() => {
if (!data || metaInjected.current) return
metaInjected.current = true
const totalValue = data.portfolio.totalValue?.toLocaleString() || '0'
const assetCount = Object.keys(data.portfolio.allocations || {}).length
document.title = `Portfolio Snapshot — ${totalValue} | Stellar Portfolio Rebalancer`
setMetaTags({
'og:title': `Portfolio Snapshot — $${totalValue}`,
'og:description': `Shared portfolio with ${assetCount} asset${assetCount !== 1 ? 's' : ''}. Total value: $${totalValue}.`,
'og:url': window.location.href,
'og:type': 'website',
'og:site_name': 'Stellar Portfolio Rebalancer',
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)PublicPortfolio\.tsx$|package\.json$|vite\.config|next\.config|ssr|server|middleware|app\.router|i18n' || true

echo
echo "PublicPortfolio outline:"
fd -a 'PublicPortfolio\.tsx' . | while read -r f; do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" --view compact || true
done

echo
echo "Relevant sections:"
fd -a 'PublicPortfolio\.tsx' . | while read -r f; do
  echo "--- $f lines 1-160"
  cat -n "$f" | sed -n '1,160p'
done

echo
echo "Meta tag usages:"
rg -n "setMetaTags|metaInjected|PublicPortfolio|og:image|Property\('<meta|Head|Head" frontend src app . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' | head -200

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 27522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "frontend/package.json:"
cat -n frontend/package.json

echo
echo "frontend/vite.config.ts:"
cat -n frontend/vite.config.ts

echo
echo "frontend/src/App.tsx route/public-share section:"
cat -n frontend/src/App.tsx | sed -n '400,455p'

echo
echo "frontend/src/config/api.ts:"
cat -n frontend/src/config/api.ts | sed -n '1,140p'

echo
echo "backend routing for share endpoints:"
rg -n "PORTFOLIO_SHARE|share|public.*portfolio|/:hash|hash" backend/src frontend/src -g '*.ts' -g '*.tsx' | head -200

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 36834


Render share metadata in the HTML response and add og:image.

This share page fetches the portfolio after mount, so initial HTTP crawlers that don’t execute this effect receive generic/no portfolio Open Graph metadata. Move the portfolio-specific tags into the crawler-facing HTML/SSR response and add an absolute og:image preview URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/PublicPortfolio.tsx` around lines 70 - 82, The
PublicPortfolio page currently injects portfolio-specific metadata only inside
the client-side useEffect, so crawlers receive generic tags. Move this metadata
generation into the server-rendered/HTML response path using the fetched
portfolio data, and add an absolute og:image preview URL while preserving the
existing title, description, URL, type, and site name values.

Source: MCP tools

Comment on lines +20 to +24
lobstr?: {
requestAccess(): Promise<{ publicKey: string }>
signTransaction(xdr: string, opts?: { networkPassphrase?: string }): Promise<string>
isConnected(): Promise<boolean>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

github.com/Lobstrco/lobstr-browser-extension @lobstrco/signer-extension-api window.lobstrSignerExtensionApi getPublicKey signTransaction xdr

💡 Result:

The @lobstrco/signer-extension-api is a JavaScript library designed to facilitate communication between web applications and the LOBSTR browser extension for the Stellar network [1][2]. When installed or included via a script tag, the library exposes a global object named window.lobstrSignerExtensionApi that allows web applications to interact directly with the user's extension [1][2][3]. Key methods available on the window.lobstrSignerExtensionApi object include: isConnected: Returns a promise that resolves to a boolean, indicating whether the LOBSTR signer extension is installed and accessible [1][2][3]. getPublicKey: Requests the user's public key from the extension [1][2]. signTransaction(xdr): This method accepts a Stellar transaction XDR string, which the extension decodes, prompts the user to sign, and subsequently returns the signed transaction to the application [1][2][3]. signMessage(message): Accepts a message string, which the extension signs and returns along with the signer's address [1][2][3]. Developers can integrate this API by installing the npm package @lobstrco/signer-extension-api or by using a CDN-hosted script tag [1][2]. Once available, the API methods are called directly from the window.lobstrSignerExtensionApi global variable [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg 'frontend/(src/utils/(stellar|walletAdapters)\.(ts|tsx)|src/components/WalletPicker\.tsx)$'

echo "== stellar.ts =="
cat -n frontend/src/utils/stellar.ts | sed -n '1,80p'

echo "== walletAdapters Lobstr section =="
cat -n frontend/src/utils/walletAdapters.ts | sed -n '190,275p'

echo "== WalletPicker section =="
cat -n frontend/src/components/WalletPicker.tsx | sed -n '1,80p'

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 9647


Use the LOBSTR signer-extension global/methods instead of window.lobstr.

LOBBSTR exposes window.lobstrSignerExtensionApi with getPublicKey(), isConnected(), and signTransaction(xdr); the current window.lobstr.requestAccess() contract is not the installed-provider API, so LOBSTR detection, connection, and signing will fail in the standard flow. Update frontend/src/utils/stellar.ts, frontend/src/utils/walletAdapters.ts, and frontend/src/components/WalletPicker.tsx to use that global and signature.

📍 Affects 3 files
  • frontend/src/utils/stellar.ts#L20-L24 (this comment)
  • frontend/src/utils/walletAdapters.ts#L218-L245
  • frontend/src/utils/walletAdapters.ts#L254-L265
  • frontend/src/components/WalletPicker.tsx#L30-L30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/stellar.ts` around lines 20 - 24, The LOBSTR integration
uses the wrong provider global and API. In frontend/src/utils/stellar.ts lines
20-24, frontend/src/utils/walletAdapters.ts lines 218-245 and 254-265, and
frontend/src/components/WalletPicker.tsx line 30, replace window.lobstr and
requestAccess with window.lobstrSignerExtensionApi, using getPublicKey(),
isConnected(), and signTransaction(xdr) for detection, connection, and signing
while preserving the existing adapter and picker behavior.

@ritik4ever
ritik4ever merged commit bd8d6c8 into ritik4ever:main Jul 30, 2026
4 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants